// ------------------------------------------------------------------------------
// 
// Copyright (c) 2006-2008 Swampware, Inc.
// 
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
// 
// ------------------------------------------------------------------------------
using System;

namespace Water
{
	/// <summary>
	/// Environment represents the current execution environment.
	/// </summary>
	public class Environment
	{
		private Environment _environment = null;
		private Water.Dictionary _constants = new Water.Dictionary();
		private Water.Dictionary _variables = new Water.Dictionary();

		public Environment()
		{
		}

		public Environment(Environment environment)
		{
			this._environment = environment;
		}

		/// <summary>
		/// This looks up an identifier in the environment.
		/// </summary>
		/// <param name="name"></param>
		/// <returns></returns>
		public object Identify(string name)
		{
			if(this._constants[name] != null)
			{
				return this._constants[name];
			}

			object value = this.Peek(name);
			if(value != null)
			{
				return value;
			}

			if(this._environment != null)
			{
				return this._environment.Identify(name);
			}

			return null;
		}

		/// <summary>
		/// Define a constant.
		/// </summary>
		/// <param name="name"></param>
		/// <param name="value"></param>
		public void Define(string name, object value)
		{
			AssertNotDefined(name);

			if(value == null)
			{
				throw new System.Exception("Cannot define \"" + name + "\" as a null value.");
			}

			this._constants.Add(name, value);
		}

		public bool IsDefined(string name)
		{
			if(this._constants[name] != null)
			{
				return true;
			}

			if(this._environment != null)
			{
				return this._environment.IsDefined(name);
			}

			return false;
		}

		private void AssertNotDefined(string name)
		{
			if(IsDefined(name))
			{
				throw new System.Exception("Constant \"" + name + "\" is already defined.");
			}
		}

		/// <summary>
		/// Push a variable.
		/// </summary>
		/// <param name="name"></param>
		/// <param name="value"></param>
		public void Push(string name, object value)
		{
			AssertNotDefined(name);

			Water.List stack;
			if (this._variables[name] == null)
			{
				stack = new Water.List();
				this._variables.Add(name, stack);
			}
			else
			{
				stack = (Water.List)this._variables[name];
			}

			stack.Push(value);
		}

		/// <summary>
		/// Pop a variable.
		/// </summary>
		/// <param name="name"></param>
		public void Pop(string name)
		{
			if (this._variables[name] == null)
			{
				throw new System.Exception(name + " is not on the variable stack.");
			}

			Water.List stack = (Water.List)this._variables[name];
			if(stack.Count == 0)
			{
				throw new System.Exception(name + " is not on the variable stack.");
			}

			stack.Pop();

			if(stack.Count == 0)
			{
				this._variables.Remove(name);
			}
		}

		/// <summary>
		/// Peek at a variable.
		/// </summary>
		/// <param name="name"></param>
		/// <returns></returns>
		public object Peek(string name)
		{
			if (this._variables[name] != null)
			{
				Water.List stack = (Water.List)this._variables[name];
				if (stack.Count > 0)
				{
					return stack.Peek();
				}
			}

			if(this._environment != null)
			{
				return this._environment.Peek(name);
			}

			return null;
		}

		public Water.List CopyVariableStack(string name)
		{
			if (this._variables[name] != null)
			{
				Water.List stack = (Water.List)this._variables[name];
				return stack.Copy();
			}
			else
			{
				return new Water.List();
			}
		}

	}
}
